package cn.o.app.json;

import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;

import android.util.Log;
import cn.o.app.reflect.OField;
import cn.o.app.reflect.ReflectUtil;

@SuppressWarnings({ "unchecked", "rawtypes" })
public class JsonUtil {

	// 反射 int->Integer.TYPE Integer->Integer.class
	public static <T> T convertFromObject(JSONObject jsonObject,
			Class<T> targetClass) throws Exception {
		return convertFromObject(jsonObject, targetClass, null);
	}

	protected static <T> T convertFromObject(JSONObject jsonObject,
			Class<T> targetClass, Type genericType) throws Exception {
		if (targetClass == String.class) {
			return (T) jsonObject.toString();
		}
		if (targetClass == JSONObject.class) {
			return (T) jsonObject;
		}
		T target = targetClass.newInstance();
		convertFromObject(jsonObject, target, genericType);
		return target;
	}

	protected static <T> void convertFromObject(JSONObject jsonObject,
			T target, Type genericType) throws Exception {
		if (target instanceof Map) {
			convertFromMap(jsonObject, (Map<?, ?>) target, genericType);
			return;
		}
		Class<?> targetClass = target.getClass();
		OField[] targetFields = OField.getFields(targetClass);
		for (OField field : targetFields) {
			String fieldName = field.getName();
			JSONField jsonField = field.getAnnotation(JSONField.class);
			if (jsonField != null && !jsonField.name().isEmpty()) {
				fieldName = jsonField.name();
			}
			if (!jsonObject.has(fieldName)) {
				continue;
			}
			Object fieldJson = jsonObject.get(fieldName);
			if (JSONObject.NULL.equals(fieldJson)) {
				continue;
			}
			Class<?> fieldClass = field.getType();
			if (fieldClass == String.class) {
				field.set(target, (String) fieldJson);
			} else if (fieldClass == Integer.TYPE
					|| fieldClass == Integer.class) {
				field.set(target, jsonObject.getInt(fieldName));
			} else if (fieldClass == Long.TYPE || fieldClass == Long.class) {
				field.set(target, jsonObject.getLong(fieldName));
			} else if (fieldClass == Double.TYPE || fieldClass == Double.class) {
				field.set(target, jsonObject.getDouble(fieldName));
			} else if (fieldClass == Boolean.TYPE
					|| fieldClass == Boolean.class) {
				field.set(target, jsonObject.getBoolean(fieldName));
			} else if (List.class.isAssignableFrom(fieldClass)) {
				if (fieldJson instanceof JSONArray) {
					field.set(
							target,
							convertFromArray((JSONArray) fieldJson, fieldClass,
									field.getGenericType()));
				}
			} else if (IJsonItem.class.isAssignableFrom(fieldClass)) {
				field.set(target, ((IJsonItem) fieldClass.newInstance())
						.fromJson(fieldJson, field));
			} else {
				if (fieldJson instanceof JSONObject) {
					field.set(
							target,
							convertFromObject((JSONObject) fieldJson,
									fieldClass, field.getGenericType()));
				}
			}
		}
	}

	public static <T> T convertFromArray(JSONArray jsonArray,
			Class<T> targetClass) throws Exception {
		return convertFromArray(jsonArray, targetClass, null);
	}

	protected static <T> T convertFromArray(JSONArray jsonArray,
			Class<T> targetClass, Type genericType) throws Exception {
		if (targetClass == String.class) {
			return (T) jsonArray.toString();
		}
		if (targetClass == JSONArray.class) {
			return (T) jsonArray;
		}
		T target = targetClass.newInstance();
		convertFromArray(jsonArray, (List<?>) target, genericType);
		return target;
	}

	protected static <T extends List> void convertFromArray(
			JSONArray jsonArray, T target, Type genericType) throws Exception {
		Class<? extends List> targetClass = target.getClass();
		Class<?> elementClass = ReflectUtil.getListElementClass(targetClass,
				genericType);
		for (int i = 0, size = jsonArray.length(); i < size; i++) {
			Object elementJson = jsonArray.get(i);
			if (JSONObject.NULL.equals(elementJson)) {
				target.add(null);
				continue;
			}
			if (elementClass == String.class) {
				target.add((String) elementJson);
			} else if (elementClass == Integer.class) {
				target.add(jsonArray.getInt(i));
			} else if (elementClass == Long.class) {
				target.add(jsonArray.getLong(i));
			} else if (elementClass == Double.class) {
				target.add(jsonArray.getDouble(i));
			} else if (elementClass == Boolean.class) {
				target.add(jsonArray.getBoolean(i));
			} else if (List.class.isAssignableFrom(elementClass)) {
				if (elementJson instanceof JSONArray) {
					target.add(convertFromArray((JSONArray) elementJson,
							elementClass, ReflectUtil.getListElementType(
									targetClass, genericType)));
				}
			} else if (IJsonItem.class.isAssignableFrom(elementClass)) {
				target.add(((IJsonItem) elementClass.newInstance()).fromJson(
						elementJson, null));
			} else {
				if (elementJson instanceof JSONObject) {
					target.add(convertFromObject((JSONObject) elementJson,
							elementClass, ReflectUtil.getListElementType(
									targetClass, genericType)));
				}
			}
		}
	}

	protected static <T> T convertFromMap(JSONObject jsonObject,
			Class<T> targetClass, Type genericType) throws Exception {
		if (targetClass == String.class) {
			return (T) jsonObject.toString();
		}
		T target = targetClass.newInstance();
		convertFromMap(jsonObject, (Map<?, ?>) target, genericType);
		return target;
	}

	protected static <T extends Map> void convertFromMap(JSONObject jsonObject,
			T target, Type genericType) throws Exception {
		Class<? extends Map> targetClass = target.getClass();
		Class<?> keyClass = ReflectUtil
				.getMapKeyClass(targetClass, genericType);
		if (!String.class.isAssignableFrom(keyClass)) {
			throw new Exception();
		}
		Class<?> elementClass = ReflectUtil.getMapElementClass(targetClass,
				genericType);
		Iterator<?> keys = jsonObject.keys();
		while (keys.hasNext()) {
			String key = keys.next().toString();
			Object elementJson = jsonObject.get(key);
			if (JSONObject.NULL.equals(elementJson)) {
				continue;
			}
			if (elementClass == String.class) {
				target.put(key, (String) elementJson);
			} else if (elementClass == Integer.class) {
				target.put(key, jsonObject.getInt(key));
			} else if (elementClass == Long.class) {
				target.put(key, jsonObject.getLong(key));
			} else if (elementClass == Double.class) {
				target.put(key, jsonObject.getDouble(key));
			} else if (elementClass == Boolean.class) {
				target.put(key, jsonObject.getBoolean(key));
			} else if (List.class.isAssignableFrom(elementClass)) {
				if (elementJson instanceof JSONArray) {
					target.put(
							key,
							convertFromArray((JSONArray) elementJson,
									elementClass, ReflectUtil
											.getMapElementType(targetClass,
													genericType)));
				}
			} else if (IJsonItem.class.isAssignableFrom(elementClass)) {
				target.put(key, ((IJsonItem) elementClass.newInstance())
						.fromJson(elementJson, null));
			} else {
				if (elementJson instanceof JSONObject) {
					target.put(
							key,
							convertFromObject((JSONObject) elementJson,
									elementClass, ReflectUtil
											.getMapElementType(targetClass,
													genericType)));
				}
			}
		}
	}

	public static <T> T convert(String str, Class<T> targetClass)
			throws Exception {
		if (str == null) {
			return null;
		}
		if (targetClass == String.class) {
			return (T) str;
		}
		return convert(str, targetClass.newInstance());
	}

	public static <T> T convert(String str, T target) throws Exception {
		JSONTokener jsonTokener = new JSONTokener(str);
		Object v = jsonTokener.nextValue();
		if (target instanceof JSONObject) {
			if (v instanceof JSONObject) {
				return (T) v;
			} else {
				throw new Exception();
			}
		}
		if (target instanceof JSONArray) {
			if (v instanceof JSONArray) {
				return (T) v;
			} else {
				throw new Exception();
			}
		}
		if (v instanceof JSONObject) {
			convertFromObject((JSONObject) v, target, null);
		} else if (v instanceof JSONArray) {
			convertFromArray((JSONArray) v, (List<?>) target, null);
		} else {
			throw new Exception();
		}
		return target;
	}

	public static <T> String convert(T target) throws Exception {
		if (target == null) {
			return null;
		}
		if (target instanceof JSONObject || target instanceof JSONArray
				|| target instanceof String || target instanceof Integer
				|| target instanceof Long || target instanceof Double
				|| target instanceof Boolean) {
			return target.toString();
		}
		if (target instanceof IJsonItem) {
			return ((IJsonItem) target).toJson(null).toString();
		}
		return ((target instanceof List) ? convertToArray(target)
				: convertToObject(target)).toString();
	}

	public static <T> JSONObject convertToObject(T target) throws Exception {
		if (target instanceof JSONObject) {
			return (JSONObject) target;
		}
		if (target instanceof Map) {
			return convertToMap(target);
		}
		JSONObject jsonObject = new JSONObject();
		Class<?> targetClass = target.getClass();
		OField[] targetFields = OField.getFields(targetClass);
		for (OField field : targetFields) {
			Object fieldValue = field.get(target);
			if (fieldValue == null) {
				continue;
			}
			String fieldName = field.getName();
			JSONField jsonField = field.getAnnotation(JSONField.class);
			if (jsonField != null && !jsonField.name().isEmpty()) {
				fieldName = jsonField.name();
			}
			if (fieldValue instanceof String) {
				jsonObject.put(fieldName, fieldValue);
			} else if (fieldValue instanceof Integer) {
				jsonObject.put(fieldName, fieldValue);
			} else if (fieldValue instanceof Long) {
				jsonObject.put(fieldName, fieldValue);
			} else if (fieldValue instanceof Double) {
				jsonObject.put(fieldName, fieldValue);
			} else if (fieldValue instanceof Boolean) {
				jsonObject.put(fieldName, fieldValue);
			} else if (fieldValue instanceof List) {
				jsonObject.put(fieldName, convertToArray(fieldValue));
			} else if (fieldValue instanceof IJsonItem) {
				jsonObject.put(fieldName,
						((IJsonItem) fieldValue).toJson(field));
			} else {
				jsonObject.put(fieldName, convertToObject(fieldValue));
			}
		}
		return jsonObject;
	}

	public static <T> JSONArray convertToArray(T target) throws Exception {
		if (target instanceof JSONArray) {
			return (JSONArray) target;
		}
		List<?> targetList = (List<?>) target;
		JSONArray jsonArray = new JSONArray();
		for (Object element : targetList) {
			if (element == null) {
				jsonArray.put(JSONObject.NULL);
				continue;
			}
			if (element instanceof String) {
				jsonArray.put(element);
			} else if (element instanceof Integer) {
				jsonArray.put(element);
			} else if (element instanceof Long) {
				jsonArray.put(element);
			} else if (element instanceof Double) {
				jsonArray.put(element);
			} else if (element instanceof Boolean) {
				jsonArray.put(element);
			} else if (element instanceof List) {
				jsonArray.put(convertToArray(element));
			} else if (element instanceof IJsonItem) {
				jsonArray.put(((IJsonItem) element).toJson(null));
			} else {
				jsonArray.put(convertToObject(element));
			}
		}
		return jsonArray;
	}

	protected static <T> JSONObject convertToMap(T target) throws Exception {
		Map<?, ?> targetMap = (Map<?, ?>) target;
		JSONObject jsonObject = new JSONObject();
		for (Entry<?, ?> entry : targetMap.entrySet()) {
			Object keyObject = entry.getKey();
			if (!(keyObject instanceof String)) {
				break;
			}
			Object element = entry.getValue();
			if (element == null) {
				continue;
			}
			String key = keyObject.toString();
			if (element instanceof String) {
				jsonObject.put(key, element);
			} else if (element instanceof Integer) {
				jsonObject.put(key, element);
			} else if (element instanceof Long) {
				jsonObject.put(key, element);
			} else if (element instanceof Double) {
				jsonObject.put(key, element);
			} else if (element instanceof Boolean) {
				jsonObject.put(key, element);
			} else if (element instanceof List) {
				jsonObject.put(key, convertToArray(targetMap.get(key)));
			} else if (element instanceof IJsonItem) {
				jsonObject.put(key, ((IJsonItem) element).toJson(null));
			} else {
				jsonObject.put(key, convertToObject(targetMap.get(key)));
			}
		}
		return jsonObject;
	}
}